package org.calconnect.icalproofing;

/**
 *	<p>ICal4jValidatorResource class</p>
 *
 *	<p>Resource that represents an iCalendar data validator using the iCal4j library.</p>
 *
 *	<p><em>Last checked in by: $Author: aron $ at $Date: 2008-10-28 13:48:00 -0700 (Tue, 28 Oct 2008) $ (UTC)</em></p>
 *
 *	@author Aron Roberts
 *	@version $Revision: 512 $
 */

/*
 * Copyright 2005-2007 Noelios Consulting.
 * 
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the "License"). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at
 * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL HEADER in each file and
 * include the License file at http://www.opensource.org/licenses/cddl1.txt If
 * applicable, add the following below this CDDL HEADER, with the fields
 * enclosed by brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 */
 
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.log4j.Logger;

import org.restlet.Context;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.ResourceException;
import org.restlet.resource.StringRepresentation;
import org.restlet.resource.Variant;
import org.restlet.util.Series;
import org.restlet.util.Variable;

public class ICal4jValidatorResource extends Resource {

	// Class variables

	// Get the name of this class
	final static String CLASSNAME = ICal4jValidatorResource.class.getName();

	// Create a log4j 'logger' for this class, based on the class name
  //
  // This allows us to identify which log events are coming from the
  // methods of this class
	public static Logger logger = Logger.getLogger( CLASSNAME );
	
	// The name of the URL attribute contained in the requested path, if any.
	final static String URL_ATTRIBUTE = "url";
	
	// Instance variables
	
	// Stores a representation of the results obtained from
	// validating iCalendar data.
	StringRepresentation validationResultsRepresentation = null;
	
	// Stores the HTTP request, so that it can be used by base class-specified
	// methods like represent() without changing their method signatures.
	Request request = null;

	/////////////////////////////////////////////////////////////////
	/**
	 *	Class constructor.
	 *
	 * @param context		The parent context.
	 *
   * @param request		The request to handle.
   *
   * @param response	The response to return.
	 */
	public ICal4jValidatorResource( Context context, Request request, Response response ) {

		super( context, request, response );
		
		logger.debug( 
			"In constructor( Context, Request, Response ) for class " + CLASSNAME + "...");
			
		// ---------------------------------------------------------------
		// Handle HTTP GET requests
		// ---------------------------------------------------------------
		
    if ( request.getMethod().equals( Method.GET ) ) {

			try {
			
				// @todo The following command assumes that a URL pointing at iCalendar data
				// was provided as part of the request path.  If we (instead or also) accept
				// a URL in a query parameter, we'll need to modify this accordingly.
				String urlStr = getAttribute( request, URL_ATTRIBUTE );

				if ( StringUtils.isNotEmpty( urlStr ) ) {
				
					// Convert the provided string into a URL object.
					URL url = null;
					try {
						url = URLUtils.urlFromString( urlStr );
					} catch ( MalformedURLException e ) {
						Throwable t = (Throwable) e;
						throw new ResourceException( Status.CLIENT_ERROR_BAD_REQUEST, t );
					}
					
					// After retrieving and storing the validation results here ...
					if ( url != null ) {
						this.validationResultsRepresentation = getValidationResults( url );
					}
					
					// ... represent(), below, will automatically retrieve
					// and return a representation of those results to the client.
				
				// Otherwise, if an error occurred during the validation attempt,
				// throw an Exception.
				//
				// @todo Provide a more helpful response here in a standard result format.
				} else {
					throw new ResourceException( 
						Status.CLIENT_ERROR_BAD_REQUEST, 
						"Could not understand the request" );
				}

			//////////////////////////////////////////////////////////////////////////////
			// Check for and return errors from HTTP GET requests.
			//////////////////////////////////////////////////////////////////////////////

			} catch ( ResourceException re ) {
			
				ErrorHandler errorHandler = new ErrorHandler( re, request );
				getResponse().setStatus( errorHandler.getStatus() );
				getResponse().setEntity( errorHandler.getEntity() );

				return;
			}
			
    }
    
		// ---------------------------------------------------------------
		// Handle HTTP POST requests
		// ---------------------------------------------------------------

    // Allow modifications of this resource via POST requests.
		setModifiable(true);

		// acceptRepresentation(), below, will automatically handle
		// processing of representations submitted via POST requests.


		// Set the types of the resources that can be returned by getRepresentation().
		//
		// @todo This is only used during server/browser content negotiation,
		// and may not actually be needed.
		getVariants().clear();
		getVariants().add( new Variant( MediaType.APPLICATION_XML ) );
		getVariants().add( new Variant( MediaType.TEXT_HTML ) );
		getVariants().add( new Variant( MediaType.TEXT_PLAIN ) );

	}

	/////////////////////////////////////////////////////////////////
	/**
	 *	Gets an attribute from the HTTP request.
	 *
	 *	@param		request			The HTTP request.
	 *
	 *	@param		attribute		A specific attribute of the HTTP request.
	 *
	 *	@return		The value, if any, of the specified attribute.
	 */
  protected String getAttribute( Request request, String attribute ) {

		logger.debug( "In getAttribute( Request, String ) in class " + CLASSNAME + "...");
		
		String value = "";
		
		value = (String) request.getAttributes().get( attribute );
		
		return value;
        
  }
  
  /////////////////////////////////////////////////////////////////
	/**
	 *	Accepts POSTed iCalendar data for validation.
	 *	Sets the HTTP response to the results of the validation attempt.
	 *
	 *	@todo Refactoring opportunity: the latter task should be placed in a new method.
	 *
	 *	@param		entity		An HTTP entity body.
	 */
	@Override
	public void acceptRepresentation( Representation entity ) throws ResourceException {

		logger.debug( "In acceptRepresentation( Representation ) in class " + CLASSNAME + "...");

		// @todo Handle submissions of iCalendar data via HTML forms,
		// as well as via a single-part entity body.
		
		String iCalStr = "";
		
		try {
		
			// If the POSTed data comes from an HTML form, then retrieve the data
			// from the first instance of a specifically-named field of that form.
			//
			// (Note: This only handles single-part forms.)
			final boolean IGNORE_PARAMETERS = true;
			if ( entity.getMediaType().equals( MediaType.APPLICATION_WWW_FORM, IGNORE_PARAMETERS ) ) {
			
				Form form = new Form( entity );
				
				// Retrieve the data from a specifically-named field of this HTML form.
				final String ICALENDAR_FORM_FIELD_NAME = "icalendar-data";
				final boolean IGNORE_CASE = true;
				iCalStr = form.getFirstValue( ICALENDAR_FORM_FIELD_NAME, IGNORE_CASE );
				
				logger.debug( "icalstr in form=" + form.toString() );
					
				// Decode the data.
				//
				// The following statement assumes the data is in the UTF-8 character set
				// and has been URI-encoded.
				if ( StringUtils.isNotEmpty( iCalStr ) ) {
					iCalStr = Reference.decode( iCalStr );
				}
				
			// Otherwise, retrieve the text of the entire body of the POST request.
			} else {
				iCalStr = entity.getText();
			}
		
			if ( StringUtils.isEmpty( iCalStr ) ) {
				ErrorHandler errorHandler = 
					new ErrorHandler( 
						"No data appears to have been submitted for validation", 
						Status.CLIENT_ERROR_BAD_REQUEST,
						this.request );
					throw errorHandler.getException();
			}
			
		} catch ( IOException e ) {
			ErrorHandler errorHandler = 
				new ErrorHandler( 
					"Error processing data submitted for validation", 
					Status.CLIENT_ERROR_BAD_REQUEST,
					e );
				throw errorHandler.getException();
		}
		
		// Use an iCalendar validator to validate the data submitted
		// via the POST request, and store the results.
		this.validationResultsRepresentation = getValidationResults( iCalStr );
		
		// @todo Refactoring opportunity:
		// Migrate the following code, which returns results
		// from the POST request, to a new method.
		
		// @todo Temporary expedient: use the error handler
		// to format success results for output 
		ErrorHandler errorHandler =
			new ErrorHandler( 
				this.validationResultsRepresentation.getText(), 
				Status.SUCCESS_OK,
				this.request );

		// Return the results of the validation attempt.
		//
		// @todo Temporary expedient: retrieve formatted results data
		// from the error handler.
		getResponse().setStatus( errorHandler.getStatus() );
		getResponse().setEntity( errorHandler.getEntity() );
	
	}
		
  /////////////////////////////////////////////////////////////////
	/**
	 *	Returns a representation of the validation results.
	 *
	 *	@return 	A representation of the validation results.
	 */
  @Override
  public Representation represent( Variant variant ) {

		logger.debug( "In represent( Variant ) in class " + CLASSNAME + "...");
		
		StringRepresentation result = null;
		
		if ( 
			this.validationResultsRepresentation == null ||
			StringUtils.isEmpty( this.validationResultsRepresentation.getText() ) 
		) {
			ErrorHandler errorHandler = 
				new ErrorHandler( 
					"No results were returned", 
					Status.SERVER_ERROR_INTERNAL,
					getRequest() );
				getResponse().setStatus( errorHandler.getStatus() );
				result = (StringRepresentation) errorHandler.getEntity();
				return result;
			// @todo Temporary expedient: use the error handler
			// to format success results for output 
			}	else {
			ErrorHandler errorHandler =
				new ErrorHandler( 
					this.validationResultsRepresentation.getText(), 
					Status.SUCCESS_OK,
					this.request );
			getResponse().setStatus( errorHandler.getStatus() );
			result = (StringRepresentation) errorHandler.getEntity();
			return result;
		}
    
  }
  
  /////////////////////////////////////////////////////////////////
	/**
	 *	Retrieves an iCalendar object from a specified URL and
	 *	returns the results from the validation attempt.
	 *
	 *	@param		url		A URL pointing to iCalendar data.
	 *
	 *	@return		The validation results.
	 *
	 *  @throws	 	ResourceException		If the data could not be obtained, or
	 *																the validation attempt failed.
	 */
	public StringRepresentation getValidationResults( URL url )
		throws ResourceException {
	
		logger.debug( "In getValidationResults( URL ) in class " + CLASSNAME + "...");
		
		ValidationResults results;
		
		// Validate the iCalendar data at the provided URL.
		try {
		
			ICal4jValidator validator = new ICal4jValidator();
			results = validator.validate( url );
			
		// Some other type of error occurred during the validation attempt.
		} catch ( ResourceException re ) {
			throw new ResourceException( re );
		}
		
		// @todo This is a simple placeholder.
		//
		// Prepare and return a uniform validation results format that can be used for both
		// valid and invalid iCalendar data, and from a variety of iCalendar proofing tools.
		return new StringRepresentation( results.getMessage() );
		
	}

  /////////////////////////////////////////////////////////////////
	/**
	 *	Retrieves an iCalendar object that has been submitted as a string
	 *	returns the results from the validation attempt.
	 *
	 *	@param		iCalStr			A String representation of an iCalendar object.
	 *
	 *	@return		The validation results.
	 *
	 *  @throws	 	ResourceException		If the data could not be obtained, or
	 *																the validation attempt failed.
	 */
	public StringRepresentation getValidationResults( String iCalStr )
		throws ResourceException {
	
		logger.debug( "In getValidationResults( String ) in class " + CLASSNAME + "...");
		
		ValidationResults results;
		
		// Validate the iCalendar data at the provided URL.
		try {
		
			ICal4jValidator validator = new ICal4jValidator();
			results = validator.validate( iCalStr );
			
		// Some other type of error occurred during the validation attempt.
		} catch ( ResourceException re ) {
			throw new ResourceException( re );
		}
		
		// @todo This is a simple placeholder.
		//
		// Prepare and return a uniform validation results format that can be used for both
		// valid and invalid iCalendar data, and from a variety of iCalendar proofing tools.
		return new StringRepresentation( results.getMessage() );
		
	}

	/////////////////////////////////////////////////////////////////

  public static void main( String[] args ) {

		// Display descriptive information about this class

  	System.out.println( CLASSNAME );
  	System.out.println( "$Revision: 512 $" );
  	System.out.println( "$Date: 2008-10-28 13:48:00 -0700 (Tue, 28 Oct 2008) $" );
  	
  }
 
}
